using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace OpenWaves.HtmlEncoding
{
    public interface IHtmlToken
    {
        void Render(IHtmlWriter writer);
    }

    public abstract class HtmlToken : IHtmlToken
    {
        public static IHtmlToken Tokenize(string html, IEnumerable<ITokenizer> tokenizers)
        {
            Contract.Requires<ArgumentNullException>(html != null);

            var tokens = new List<HtmlToken>();

            foreach (var tokenizer in tokenizers)
            {
                tokens.AddRange(tokenizer.Tokenize(html));
            }

            var root = new RootToken(0, html.Length-1);
            foreach (var token in tokens.OrderBy(t => t.StartIndex))
            {
                root.AddChild(token);
            }

            root.AddMissingTokensRecursively(html);

            return root;            
        }

        public int StartIndex { get; private set; }
        public int EndIndex{ get; private set; }
        
        private readonly List<HtmlToken> children = new List<HtmlToken>();
        public IEnumerable<HtmlToken> Children
        {
            get { return this.children; }
        }

        protected HtmlToken(int startIndex, int endIndex)
        {
            StartIndex = startIndex;
            EndIndex =  endIndex;
        }

        public void AddChild(HtmlToken htmlToken)
        {
            var parent = Children.SingleOrDefault(t => t.StartIndex <= htmlToken.StartIndex && t.EndIndex >= htmlToken.EndIndex);

            if (parent != null)
            {
                parent.AddChild(htmlToken);
            }
            else
            {
                this.children.Add(htmlToken);
            }
        }

        protected void AddMissingTokensRecursively(string text)
        {
            this.AddMissingTokens(text);    
            foreach(var child in this.Children)
            {
                child.AddMissingTokensRecursively(text);
            }
        }

        protected virtual void AddMissingTokens(string html)
        {
            AddMissingTokens(html, this.StartIndex, this.EndIndex);
        }

        protected void AddMissingTokens(string html, int startIndex, int endIndex)
        {
            Contract.Requires<ArgumentOutOfRangeException>(startIndex >= 0);
            Contract.Requires<ArgumentOutOfRangeException>(endIndex < html.Length);

            var i = 0;
            var lastTokenEndIndex = startIndex - 1;
            foreach (var token in new List<HtmlToken>(Children))
            {
                if (lastTokenEndIndex + 1 < token.StartIndex)
                    this.children.Insert(i++, new RawTextToken(html, lastTokenEndIndex + 1, token.StartIndex - 1));

                lastTokenEndIndex = token.EndIndex;
                i++;
            }

            if (lastTokenEndIndex < endIndex)
            {
                this.children.Insert(i, new RawTextToken(html, lastTokenEndIndex + 1, endIndex));
            }
        }

        public abstract void Render(IHtmlWriter writer);
    }
}